/****************************************************************************
 *
 * Copyright (C) 2003-2008 Los Alamos National Security, LLC
 *                         Packet Analytics Corporation
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License Version 2 as
 * published by the Free Software Foundation.  You may not use, modify or
 * distribute this program under any other version of the GNU General
 * Public License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 ****************************************************************************/
package nfse.query;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Vector;

import nfse.NetFSE;
import nfse.SensorInfo;
import nfse.result.*;

public class Query implements Cloneable {

	public static final int QUERY_NON_LEAF = 1;

	public static final int QUERY_QUASI_LEAF = 2;

	public static final int QUERY_LEAF = 3;

	public static final int QUERY_EMPTY = 4;

	public static final int QUERY_INVALID = 5;

	private Vector<Criteria> criteria = new Vector<Criteria>();

	private Vector<Data> data = new Vector<Data>();

	private Vector<DateRange> dates = new Vector<DateRange>();

	private Vector<Query> joins = new Vector<Query>();

	private Query parent;

	private Vector<Query> queries = new Vector<Query>();

	private Result result = null;

	private Vector<TimeRange> times = new Vector<TimeRange>();

	private Vector<TimestampRange> timestampRanges = new Vector<TimestampRange>();

	private int queryID = -1;

	private boolean distribute = true;

	private boolean reply = false;

	private String password = "";

	private String user = "";

	private String userIP = "";

	private String sessionID = "";

	private String znumber = "";

	public Query(int queryID) {
		this.queryID = queryID;
	}

	public Query() {
	}

	public Query detach(int newID) throws QueryException {
		// if (parent == null)
		// return this;
		// System.out.println("Detaching: " + this.toXML());
		Query temp = (Query) this.clone();
		temp.criteria = new Vector<Criteria>();
		Criteria criteria[] = this.getCriteria();
		for (int i = 0; i < criteria.length; i++)
			temp.criteria.addElement(criteria[i]);
		temp.data = new Vector<Data>();
		Data data[] = this.getData();
		for (int i = 0; i < data.length; i++)
			temp.data.addElement(data[i]);
		temp.dates = new Vector<DateRange>();
		DateRange dates[] = this.getDateRanges();
		for (int i = 0; i < dates.length; i++)
			temp.dates.addElement(dates[i]);
		temp.queries = new Vector<Query>();
		Query queries[] = this.getSubqueries();
		for (int i = 0; i < queries.length; i++)
			temp.queries.addElement(queries[i]);
		temp.times = new Vector<TimeRange>();
		TimeRange times[] = this.getTimeRanges();
		for (int i = 0; i < times.length; i++)
			temp.times.addElement(times[i]);
		TimestampRange timestamps[] = this.getTimestampRanges();
		for (int i = 0; i < timestamps.length; i++)
			temp.addTimestampRange(timestamps[i]);
		temp.parent = null;
		temp.setResult(this.getResult());
		temp.setUser(this.getUser());
		temp.setPassword(this.getPassword());
		temp.setUserIP(this.getUserIP());
		temp.setSessionID(this.getSessionID());
		temp.setZNumber(this.getZNumber());

		temp.setID(newID);
		// temp.recordShaper = this.getRecordShaper();
		// System.out.println("Detached: " + temp.toXML());
		return temp;
	}

	public void setReply(boolean reply) {
		this.reply = reply;
	}

	public boolean needReply() {
		if (parent != null)
			return parent.needReply();
		return reply;
	}

	public int getID() {
		return queryID;
	}

	public void setID(int queryID) {
		this.queryID = queryID;
	}

	public void addCriteria(Criteria c) throws QueryException {
		if (c != null) {
			for (int i = 0; i < criteria.size(); i++) {
				Criteria c2 = (Criteria) criteria.elementAt(i);
				if (c2.compareTo(c) == 0) {
					// System.out.println("Criteria exists!");
					return;
				}
			}
			criteria.addElement(c);
		}
	}

	public void addData(Data datatype) throws QueryException {
		int type = datatype.type;
		if (parent != null) {
			return; // should we throw an exception here? 8/3/2004
		}
		if (!NetFSE.isTypeValid(type))
			throw (new QueryException("Cannot add invalid specified type: "
					+ type));
		if ((type == NetFSE.TYPE_ALL) && (datatype.sensor == -1)) {
			// If ALL is specified remove all other types and use ALL
			data.removeAllElements();
			data.addElement(datatype);
			return;
		}
		if (data.size() == 1) {
			Data d = (Data) data.elementAt(0);
			if (d.type == NetFSE.TYPE_ALL) {
				data.removeAllElements();
			}
		}
		for (int i = 0; i < data.size(); i++) {
			Data d = (Data) data.elementAt(i);
			if (d.compareTo(datatype) == 0)
				return; // The types are the same, don't bother adding it
		}
		data.addElement(datatype);
	}

	public void addDate(QueryDate date) throws QueryException {
		if (date == null)
			return;
		DateRange dr = new DateRange(date);
		addDateRange(dr);
	}

	public void addDateRange(DateRange dateRange) throws QueryException {
		if (dateRange == null)
			return;

		// need to fix this 8/4/2004
		// if (queries.size() > 0)
		// throw (new QueryException("Cannot add a date to a query containing
		// subqueries."));
		for (int i = 0; i < dates.size(); i++) {
			DateRange dr = (DateRange) dates.elementAt(i);
			if (dr.compareTo(dateRange) == 0) {
				return;
			} else if ((dr.extendsStart(dateRange))
					|| (dr.extendsEnd(dateRange))) {
				dr.extend(dateRange);
				return;
			}
		}
		dates.addElement(dateRange);
		sortDates();
	}

	/*
	 * public void addResult(Result result) { if (result != null) {
	 * results.addElement(result); } }
	 */
	public void addSubquery(Query sub) throws QueryException {
		if (sub == null)
			return;
		sub.setParent(this);
		queries.addElement(sub);
	}

	public void addTimeRange(TimeRange timeRange) throws QueryException {
		if (timeRange == null)
			return;
		if (this.getParent() != null)
			throw (new QueryException("Cannot add a time to a subquery."));
		for (int i = 0; i < times.size(); i++) {
			TimeRange tr = (TimeRange) times.elementAt(i);
			if (tr.compareTo(timeRange) == 0) {
				return;
			} else if ((tr.extendsStart(timeRange))
					|| (tr.extendsEnd(timeRange))) {
				tr.extend(timeRange);
				return;
			}
		}
		times.addElement(timeRange);
		sortTimes();
	}

	public void addTimestampRange(TimestampRange timestampRange)
			throws QueryException {
		boolean found = false;
		for (int i = 0; i < timestampRanges.size(); i++) {
			TimestampRange tsr = (TimestampRange) timestampRanges.elementAt(i);
			if ((tsr.startTS.getTime() == timestampRange.startTS.getTime())
					&& (tsr.endTS.getTime() == timestampRange.endTS.getTime())) {
				found = true;
				break;
			}
		}
		if (!found)
			timestampRanges.addElement(timestampRange);
	}

	public TimestampRange[] getTimestampRanges() {
		if ((timestampRanges.size() == 0) && (parent == null)) {
			TimestampRange[] temp = new TimestampRange[0];
			return temp;
		} else if (timestampRanges.size() == 0) {
			return parent.getTimestampRanges();
		}

		TimestampRange[] temp = new TimestampRange[timestampRanges.size()];
		for (int i = 0; i < temp.length; i++) {
			temp[i] = (TimestampRange) timestampRanges.elementAt(i);

			// System.out.println("GET TIMESTAMP RANGE: " + temp[i].toXML());
		}
		return temp;
	}

	public String getUser() {
		if (parent != null)
			return parent.getUser();
		return user;
	}

	public String getPassword() {
		if (parent != null)
			return parent.getPassword();
		return password;
	}

	public String getUserIP() {
		if (parent != null)
			return parent.getUserIP();
		return userIP;
	}

	public String getSessionID() {
		if (parent != null)
			return parent.getSessionID();
		return sessionID;
	}

	public Object clone() {
		Query q = null;
		try {
			q = new Query();
			// q.setRecordShaper(recordShaper);
			q.setParent(this.getParent());
			for (int x = 0; x < queries.size(); x++)
				q.addSubquery((Query) queries.elementAt(x));
			for (int i = 0; i < data.size(); i++)
				q.addData((Data) data.elementAt(i));
			for (int i = 0; i < dates.size(); i++)
				q.addDateRange((DateRange) dates.elementAt(i));
			for (int i = 0; i < times.size(); i++)
				q.addTimeRange((TimeRange) times.elementAt(i));
			for (int i = 0; i < timestampRanges.size(); i++)
				q.addTimestampRange((TimestampRange) timestampRanges
						.elementAt(i));
			for (int i = 0; i < criteria.size(); i++)
				q.addCriteria((Criteria) criteria.elementAt(i));
			// for (int i = 0; i < results.size(); i++)
			// q.addResult((Result) results.elementAt(i));
			q.setResult(result);
			q.setUser(user);
			q.setPassword(password);
			q.setUserIP(this.getUserIP());
			q.setSessionID(this.getSessionID());
			q.setZNumber(this.getZNumber());
			q.setDistribute(this.distribute);
			// q.setID(queryID);

		} catch (QueryException qe) {
			qe.printStackTrace();
		}
		return (Object) q;
	}

	public Query[] decomposeByDataType() {
		// System.out.println("Decompose by type.");
		Query[] temp = new Query[1];
		temp[0] = this;
		Data[] data = this.getData();

		try {
			if ((data.length == 0) && (joins.size() == 0)
					&& (queries.size() == 0)) { // Assume
				// ALL
				temp = new Query[NetFSE.getTypeNames().length - 1];
				for (int i = 0; i < temp.length; i++) {
					temp[i] = (Query) this.clone();
					temp[i].setData(new Data(i + 1, -1));
				}
				/*
				 * Query.marshallResults( temp, host, this.queryID,
				 * NetForSE.RESULT_SERVER_PORT);
				 */
				return temp;
			} else if (data.length == 1) {
				Data d = (Data) data[0];
				if (d.type == NetFSE.TYPE_ALL) {
					temp = new Query[NetFSE.getTypeNames().length - 1];
					for (int i = 0; i < temp.length; i++) {
						temp[i] = (Query) this.clone();
						temp[i].setData(new Data(i + 1, -1));
					}
					/*
					 * Query.marshallResults( temp, host,
					 * NetForSE.RESULT_SERVER_PORT, this.queryID);
					 */
					return temp;
				}
			} else {
				Vector<Query> tempQueries = new Vector<Query>();
				ArrayList<SensorInfo> sensors = NetFSE.getSensorInfo();
				for (int x = 0; x < data.length; x++) {
					for (int i = 0; i < sensors.size(); i++) {
						if (sensors.get(i).getToolID() == data[x].type) {
							Query q = (Query) this.clone();
							q.setData(data[x]);
							tempQueries.addElement(q);
							break;
						}
					}
				}
				temp = new Query[tempQueries.size()];
				for (int i = 0; i < temp.length; i++) {
					temp[i] = (Query) tempQueries.elementAt(i);
				}
			}
			/*
			 * Query.marshallResults( temp, host, NetForSE.RESULT_SERVER_PORT,
			 * this.queryID);
			 */
			return temp;
		} catch (Exception E) {
			E.printStackTrace();
		}
		return temp;
	}

	public Query[] decomposeBySensor() {
		// System.out.println("Decompose by sensor.");
		Query[] temp = new Query[1];
		temp[0] = this;
		Data[] data = this.getData();
		try {
			ArrayList<SensorInfo> sensors = NetFSE.getSensorInfo();
			if ((data.length == 0) && (joins.size() == 0)
					&& (queries.size() == 0)) { // Assume
				// ALL
				Vector<Query> queries = new Vector<Query>();
				for (int i = 0; i < NetFSE.getTypeNames().length; i++) {
					for (int x = 0; x < sensors.size(); x++) {
						SensorInfo sensor = sensors.get(x);
						if (sensor.getToolID() == i + 1) {
							Query q = (Query) this.clone();
							Data d = new Data(sensor.getToolID(), sensor
									.getSensorID());
							q.setData(d);
							// System.out.println("Data: " + d.toXML());
							queries.addElement(q);
						}
					}
				}
				temp = new Query[queries.size()];
				for (int i = 0; i < temp.length; i++)
					temp[i] = (Query) queries.elementAt(i);
				// Query.marshallResults(
				// temp,
				// host,
				// this.queryID,
				// NetForSE.RESULT_SERVER_PORT);
				return temp;
			}
			if (data.length == 1) {
				Data d = data[0];
				if (d.type == NetFSE.TYPE_ALL) {
					Vector<Query> queries = new Vector<Query>();
					for (int i = 0; i < NetFSE.getTypeNames().length; i++) {
						for (int x = 0; x < sensors.size(); x++) {
							SensorInfo sensor = sensors.get(x);
							if (sensor.getToolID() == i + 1) {
								Query q = (Query) this.clone();
								Data d2 = new Data(sensor.getToolID(), sensor
										.getSensorID());
								q.setData(d2);
								// System.out.println("Data: " + d2.toXML());
								queries.addElement(q);
							}
						}
					}
					temp = new Query[queries.size()];
					for (int i = 0; i < temp.length; i++)
						temp[i] = (Query) queries.elementAt(i);
					// Query.marshallResults(
					// temp,
					// host,
					// this.queryID,
					// NetForSE.RESULT_SERVER_PORT);
					return temp;
				}
			}
			Vector<Query> queries = new Vector<Query>();
			for (int i = 0; i < data.length; i++) {
				Data d = data[i];
				if (d.sensor == -1) {
					for (int x = 0; x < sensors.size(); x++) {
						SensorInfo sensor = sensors.get(x);
						if (sensor.getToolID() == d.type) {
							Query q = (Query) this.clone();
							Data d2 = new Data(d.type, sensor.getSensorID());
							d2.limit = d.limit;
							q.setData(d2);
							// System.out.println("Data: " + d2.toXML());
							queries.addElement(q);
						}
					}
				} else {
					Query q = (Query) this.clone();
					Data d2 = new Data(d.type, d.sensor);
					q.setData(d2);
					// System.out.println("Data: " + d2.toXML());
					queries.addElement(q);
				}
			}
			temp = new Query[queries.size()];
			for (int i = 0; i < temp.length; i++)
				temp[i] = (Query) queries.elementAt(i);
			// Query.marshallResults(
			// temp,
			// host,
			// this.queryID,
			// NetForSE.RESULT_SERVER_PORT);
			return temp;
		} catch (Exception E) {
			E.printStackTrace();
		}
		/*
		 * try { Query.marshallResults( temp, host, this.queryID,
		 * NetForSE.RESULT_SERVER_PORT); } catch (QueryException qe) {
		 * qe.printStackTrace(); }
		 */
		return temp;
	}

	/*
	 * public Query[] decomposeByDateRange() { this.marshallSocketResults();
	 * Query[] temp = new Query[1]; temp[0] = this; try { if (dates.size() == 0)
	 * { return temp; } temp = new Query[dates.size()]; for (int i = 0; i <
	 * temp.length; i++) { Query q = (Query) this.clone();
	 * q.setDateRange((DateRange) dates.elementAt(i)); temp[i] = q; } return
	 * temp; } catch (Exception E) { E.printStackTrace(); } return temp; }
	 * 
	 * 
	 * 
	 * public Query[] decomposeByTimeRange() { this.marshallSocketResults();
	 * Query[] temp = new Query[1]; temp[0] = this; try { if (times.size() == 0)
	 * { return temp; } temp = new Query[times.size()]; for (int i = 0; i <
	 * temp.length; i++) { Query q = (Query) this.clone();
	 * q.setTimeRange((TimeRange) times.elementAt(i)); temp[i] = q; } return
	 * temp; } catch (Exception E) { E.printStackTrace(); } return temp; }
	 */

	public Criteria[] getCriteria() {
		if ((criteria.size() == 0) && (parent == null)) {
			Criteria[] temp = new Criteria[0];
			return temp;
		} else if (criteria.size() == 0) {
			return parent.getCriteria();
		}
		Criteria[] temp = new Criteria[criteria.size()];
		for (int i = 0; i < temp.length; i++)
			temp[i] = (Criteria) criteria.elementAt(i);
		return temp;
	}

	public Data[] getData() {
		if ((data.size() == 0) && (parent == null)) {
			Data[] temp = new Data[0];
			return temp;
		} else if (data.size() == 0) {
			return parent.getData();
		}
		Data[] temp = new Data[data.size()];
		for (int i = 0; i < temp.length; i++)
			temp[i] = (Data) data.elementAt(i);
		return temp;
	}

	public DateRange[] getDateRanges() {
		if ((dates.size() == 0) && (parent == null)) {
			DateRange[] temp = new DateRange[0];
			return temp;
		} else if (dates.size() == 0) {
			return parent.getDateRanges();
		}
		DateRange[] temp = new DateRange[dates.size()];
		for (int i = 0; i < temp.length; i++)
			temp[i] = (DateRange) dates.elementAt(i);
		return temp;
	}

	public int getNumDataTypes() {
		if ((data.size() == 0) && (parent == null)) {
			return 0;
		} else if (data.size() == 0) {
			return parent.getData().length;
		}
		return data.size();
	}

	// public int getNumDateRanges() {
	// return dates.size();
	// }

	// public int getNumJoins() {
	// return joins.size();
	// }

	public int getNumSubqueries() {
		return queries.size();
	}

	// public int getNumTimeRanges() {
	// return times.size();
	// }

	public Query getParent() {
		// if (parent == null)
		// System.out.println("Parent was null for #" + this.queryID);
		// else
		// System.out.println("Parent was #" + parent.queryID + " for #" +
		// this.queryID);
		return parent;
	}

	public Query[] getQueriesBySocketResult(SocketResult sr) {
		Query root = getRoot();
		return root.getSubqueriesBySocketResult(sr);
	}

	// public Result getResult() { // This method should not be used now that
	// multiple Result elements are allowed
	// Result[] results = this.getResults();
	// if (results.length == 0)
	// return null;
	// return results[0];
	// }

	public Result getResult() {
		if (parent == null)
			return result;
		if (result != null)
			return result;
		return parent.getResult();
	}

	public Query getRoot() {
		if (parent == null)
			return this;
		return parent.getRoot();
	}

	public Query[] getSubqueries() {
		Query[] temp = new Query[queries.size()];
		for (int i = 0; i < temp.length; i++)
			temp[i] = (Query) queries.elementAt(i);
		return temp;
	}

	public Query[] getSubqueriesBySocketResult(SocketResult sr) {
		// Query root = getRoot();
		Query[] temp = null;
		Vector<Query> temp2 = new Vector<Query>();
		for (int i = 0; i < queries.size(); i++) {
			Query q = (Query) queries.elementAt(i);
			Query[] temp3 = q.getSubqueriesBySocketResult(sr);
			for (int j = 0; j < temp3.length; j++)
				temp2.addElement(temp3[j]);
			// for (int j = 0; j < results.size(); j++) {
			try {
				SocketResult sr2 = (SocketResult) result;
				// if ((sr2.getMarshall() == null)
				// && (sr2.host == sr.host)
				// && (sr2.port == sr.port)) {
				if ((sr2.host == sr.host) && (sr2.port == sr.port)) {
					temp2.addElement(q);
					break;
				}
			} catch (ClassCastException cce) {
				// do nothing, not a SocketResult
			}
			// }
		}
		temp = new Query[temp2.size()];
		for (int i = 0; i < temp.length; i++)
			temp[i] = (Query) temp2.elementAt(i);
		return temp;
	}

	public TimeRange[] getTimeRanges() {
		if ((times.size() == 0) && (parent == null)) {
			TimeRange[] temp = new TimeRange[0];
			return temp;
		} else if (times.size() == 0) {
			return parent.getTimeRanges();
		}
		TimeRange[] temp = new TimeRange[times.size()];
		for (int i = 0; i < temp.length; i++)
			temp[i] = (TimeRange) times.elementAt(i);
		return temp;
	}

	public boolean hasParent() {
		return (parent != null);
	}

	public boolean hasResult() {
		return result != null;
	}

	public boolean isResultCovered() {
		return true;
	}

	public static ResultMarshall marshallResults(Query[] queries, String host,
			int port, int queryID) throws QueryException {
		return marshallResults(queries, host, port, queryID, 1);
	}

	/***************************************************************************
	 * Method: marshallResults Parms: queries - array of Query to be marshalled
	 * host - Sting defining host to handle the results port - int defining port
	 * on host queryID - the super-query that the queries entries are associated
	 * with Throws: QueryException if the queries parm is null or empty or if
	 * the queries elements don't have the same result Purpose: Creates a result
	 * marshall to process the results of the queries.
	 **************************************************************************/
	public static ResultMarshall marshallResults(Query[] queries, String host,
			int port, int queryID, int numQueryProcessors)
			throws QueryException {
		// System.out.println("Marshalling results for query ID " + queryID);
		if ((queries == null) || (queries.length == 0)) {
			throw (new QueryException(
					"Attempted to marshall a null or empty set of queries."));
		}
		Result r = queries[0].getResult();

		if (queryID == -1)
			throw (new QueryException(
					"Attempting to marshall results to a query without a valid ID."));
		MarshalledResult mr = new MarshalledResult(host, port, queryID);
		for (int i = 0; i < queries.length; i++) {
			// Result r2 = queries[i].getResult();
			queries[i].setResult(mr);
			// System.out.println("Marshalled: " + queries[i].toXML());
			// System.out.println("Need reply for query #" + queryID);
		}
		ResultMarshall rm = new ResultMarshall(r, queryID);
		try {
			rm.setPending(queries.length * numQueryProcessors);
		} catch (InterruptedException ie) {
			ie.printStackTrace();
		}
		rm.start();
		return rm;
	}

	public void setCriteria(Criteria c) throws QueryException {
		criteria.removeAllElements();
		addCriteria(c);
	}

	public void setData(Data datatype) throws QueryException {
		if (parent != null) {
			return; // should we throw an exception here? 8/3/2004
		}
		data.removeAllElements();
		addData(datatype);
	}

	public void setDate(QueryDate date) throws QueryException {
		if (date == null) {
			dates.removeAllElements();
			return;
		}
		if (queries.size() > 0)
			throw (new QueryException(
					"Cannot set the date of a query containing subqueries."));
		dates.removeAllElements();
		DateRange d = new DateRange(date);
		addDateRange(d);
	}

	public void setDateRange(DateRange dateRange) throws QueryException {
		if (dateRange == null) {
			dates.removeAllElements();
			return;
		}
		if (queries.size() > 0) {
			throw (new QueryException(
					"Cannot set a date range for a query containing subqueries."));
		}
		dates.removeAllElements();
		addDateRange(dateRange);
	}

	public void setParent(Query query) throws QueryException {
		// if ((parent != null) && (parent != query))
		// throw (new QueryException("Cannot change the parent of the query."));
		this.parent = query;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public void setUserIP(String userIP) {
		this.userIP = userIP;
	}

	public void setSessionID(String sessionID) {
		this.sessionID = sessionID;
	}

	public void setZNumber(String znumber) {
		this.znumber = znumber;
	}

	public String getZNumber() {
		if (parent != null)
			return parent.getZNumber();
		return znumber;
	}

	/*
	 * public boolean joinPending() { return joinPending; }
	 * 
	 * public void setJoinPending(boolean pending) { this.joinPending = pending;
	 * }
	 * 
	 * public void newJoin() throws QueryException { joinPending = true; if
	 * ((criteria.size() > 0) || (queries.size() > 0)) { throw ( new
	 * QueryException("Cannot add a join to a query with subqueries or
	 * criteria.")); } }
	 * 
	 * public void addToJoin(Query query) throws QueryException { if
	 * (!joinPending) throw ( new QueryException("Cannot add to a join unless a
	 * join is pending.")); tempJoins.addElement(query); }
	 * 
	 * 
	 * public void closeJoin() throws QueryException { if (!joinPending) { throw
	 * ( new QueryException("Cannot close a join unless a join is pending.")); }
	 * Query[] temp = new Query[tempJoins.size()]; for (int i = 0; i <
	 * temp.length; i++) temp[i] = (Query) tempJoins.elementAt(i);
	 * tempJoins.removeAllElements(); addJoin(temp); }
	 * 
	 * 
	 * public void addJoin(Query[] toJoin) throws QueryException { if
	 * (toJoin.length == 0) return; if ((criteria.size() > 0) || (queries.size()
	 * > 0)) { throw ( new QueryException("Cannot add a join to a query with
	 * subqueries or criteria.")); } Vector temp = new Vector(); for (int i = 0;
	 * i < toJoin.length; i++) temp.addElement(toJoin[i]);
	 * joins.addElement(temp); }
	 * 
	 * public void setJoin(Query[] toJoin) throws QueryException {
	 * joins.removeAllElements(); addJoin(toJoin); }
	 */

	public void setResult(Result result) throws QueryException {
		// if (result == null)
		// throw (new QueryException("Tried to set the query result to null."));
		if (this.parent != null) {
			if (!(result instanceof MarshalledResult))
				throw (new QueryException(
						"Cannot set the result type of a subquery unless a MarshalledResult or DatabaseResult"));
		}
		this.result = result;
	}

	public void setTimeRange(TimeRange timeRange) throws QueryException {
		if (timeRange == null)
			return;
		if (queries.size() > 0)
			throw (new QueryException(
					"Cannot set a time range for a query containing subqueries."));
		times.removeAllElements();
		addTimeRange(timeRange);
	}

	public void setUser(String user) {
		this.user = user;
	}

	private void sortDates() {
		DateRange[] temp = new DateRange[dates.size()];
		for (int i = 0; i < temp.length; i++)
			temp[i] = (DateRange) dates.elementAt(i);
		Arrays.sort(temp);
		dates.removeAllElements();
		for (int i = 0; i < temp.length; i++)
			dates.addElement(temp[i]);
	}

	private void sortTimes() {
		TimeRange[] temp = new TimeRange[times.size()];
		for (int i = 0; i < temp.length; i++)
			temp[i] = (TimeRange) times.elementAt(i);
		Arrays.sort(temp);
		times.removeAllElements();
		for (int i = 0; i < temp.length; i++)
			times.addElement(temp[i]);
	}

	public void setDistribute(boolean distribute) {
		this.distribute = distribute;
	}

	public boolean isDistribute() {
		return distribute;
	}

	/*
	 * public Query[] getJoin(int idx) { Query[] temp = new Query[((Vector)
	 * joins.elementAt(idx)).size()]; for (int i = 0; i < temp.length; i++)
	 * temp[i] = (Query) (((Vector) joins.elementAt(idx)).elementAt(i)); return
	 * temp; }
	 */

	public String toString() {
		return this.toXML();
	}

	public String toXML() {
		boolean dataDone = false;
		boolean dateDone = false;
		String temp = "";
		temp = "<Query id=\"" + this.queryID + "\" ";
		if ((user.length() > 0) && (password.length() > 0)) {
			temp += "user=\"" + user + "\" password=\"" + password + "\" ";
		} else {
			temp += "userip=\"" + userIP + "\" sessionid=\"" + sessionID
					+ "\" ";
		}
		temp += "distribute=\"" + this.distribute + "\" ";
		Data data[] = this.getData();
		if (data.length == 1) {
			temp += " type=\"" + data[0].type + "\" ";
			if (data[0].sensor != -1) {
				temp += " sensor=\"" + data[0].sensor + "\" ";
			}
			dataDone = true;
		}
		DateRange[] dates = this.getDateRanges();
		if (dates.length == 1) {
			if (!dates[0].isRange()) {
				temp += " date=\"" + dates[0].getStartDate() + "\"";
				dateDone = true;
			}
		}
		Criteria[] criteria = this.getCriteria();
		for (int i = 0; i < criteria.length; i++)
			temp += " " + criteria[i];
		temp += ">";
		TimeRange[] times = this.getTimeRanges();
		for (int i = 0; i < times.length; i++)
			temp += times[i].toXML();
		TimestampRange[] timestamps = this.getTimestampRanges();
		for (int i = 0; i < timestamps.length; i++)
			temp += timestamps[i].toXML();

		if (!dateDone) {
			for (int i = 0; i < dates.length; i++)
				temp += dates[i].toXML();
		}
		if (!dataDone) {
			for (int i = 0; i < data.length; i++)
				temp += data[i].toXML();
		}
		for (int i = 0; i < queries.size(); i++) {
			temp += ((Query) queries.elementAt(i)).toXML();
		}
		for (int i = 0; i < queries.size(); i++) {
			Query query = (Query) queries.elementAt(i);
			temp += query.toXML();
		}
		temp += this.getResult().toXML();
		// if (recordShaper != null)
		// temp += recordShaper.toXML();
		temp += "</Query>";
		return temp;
	}

	public String toNoPasswordXML() {
		String xml = this.toXML();
		int pwdIdx = xml.indexOf("password=\"");

		if (pwdIdx != -1) {
			int q1 = xml.indexOf("\"", pwdIdx);
			int q2 = xml.indexOf("\"", q1 + 1);
			return xml.substring(0, pwdIdx) + "password=\"xxxxxxxx\" "
					+ xml.substring(q2);
		} else
			return xml;
	}

	public int validate() {
		int numDates = this.getDateRanges().length;
		int numTimestamps = this.getTimestampRanges().length;
		int numCriteria = this.getCriteria().length;
		if (((numTimestamps == 0) && (numDates == 0)) && (numCriteria == 0)
				&& (joins.size() == 0) && (queries.size() == 0)) {
			return Query.QUERY_EMPTY;
		}

		// if ((user.length() == 0) || (password.length() == 0))
		// return NetForSE.QUERY_INVALID;

		if ((((numTimestamps == 0) && (numDates == 0)) || (numCriteria == 0))
				&& (joins.size() == 0) && (queries.size() == 0)) {
			return Query.QUERY_INVALID;
		}

		if ((joins.size() == 0) && (queries.size() == 0)) {
			// System.out.println("HERE");
			// if (types.size() == 0)
			// return NetForSE.QUERY_EMPTY;
			if (((numTimestamps == 0) && (numDates == 0)) || (numCriteria == 0))
				return Query.QUERY_INVALID;
			if (((numTimestamps > 1) || (numDates > 0)) || (numCriteria > 1))
				return Query.QUERY_QUASI_LEAF;
			// DateRange dr = (DateRange)dates.elementAt(0);
			// if (!dr.isRange())
			// return NetForSE.QUERY_LEAF;
			return Query.QUERY_QUASI_LEAF;
		} else {
			int val = Query.QUERY_EMPTY;
			for (int i = 0; i < joins.size(); i++) {
				Query q = (Query) joins.elementAt(i);
				int val2 = q.validate();
				if (val2 == Query.QUERY_INVALID)
					return Query.QUERY_INVALID;
				if (val2 != Query.QUERY_EMPTY)
					val = Query.QUERY_QUASI_LEAF;
			}
			for (int i = 0; i < queries.size(); i++) {
				Query q = (Query) queries.elementAt(i);
				int val2 = q.validate();
				// System.out.println("Subquery at " + i + " validate=" + val2);
				if (val2 == Query.QUERY_INVALID)
					return Query.QUERY_INVALID;
				if (val2 != Query.QUERY_EMPTY)
					val = Query.QUERY_QUASI_LEAF;
			}
			return val;
		}
		// return NetForSE.QUERY_INVALID;
	}

}